package vp.remedies.dr.home.db;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import vp.remedies.dr.home.so.SOObject;
import vp.remedies.dr.home.utils.Utility;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;

/**
 * Helper class for all the operations on <tt>SQLiteDatabase</tt> in
 * <tt>Android</tt>. <br>
 * <br>
 * Currently the following operations on Database are supported:<br>
 * <ul>
 * <li>Create Database
 * <li>Insert Row(s)
 * <li>Update Row(s)
 * <li>Delete Row(s)
 * <li>Select Row(s)
 * </ul>
 */
public class AndroidSQLiteHelper extends SQLiteOpenHelper {

	// The Android's default system path of your application database.
	private final String DEFAULT_ANROID_DB_LOCATION = "/data/data/";

	private final static String DATABASE_NAME = "remediesDB";

	private SQLiteDatabase applicationDatabase;

	private Context mContext = null;

	/**
	 * Constructor for initialization
	 * 
	 * @param context
	 *            Context of execution (MClientActivity)
	 * @param version
	 *            DB Version
	 * @throws IOException
	 */
	public AndroidSQLiteHelper(Context context, int version) throws IOException {
		super(context, DATABASE_NAME, null, version);
		mContext = context;
	}

	/**
	 * Singleton instance
	 */
	private static AndroidSQLiteHelper instance;

	/**
	 * To be used only from <code>DatabaseInitTask</code> for instantiating
	 * AndroidSQLiteHelper.
	 * 
	 * @param androidSQLiteHelper
	 * @throws Exception
	 */
	/*
	 * This method is provided to enforce singleton behavior of
	 * AndroidSQLiteHelper. The constructor requires Activity and DB version for
	 * instantiation and hence a setInstance() and getInstance() are provided
	 * separately.
	 */
	public static void setInstance(AndroidSQLiteHelper androidSQLiteHelper)
			throws Exception {
		if (instance == null) {
			instance = androidSQLiteHelper;
			try {
				instance.initDataBase();
			} catch (IOException e) {
				throw e;
			}
		}
	}

	public static AndroidSQLiteHelper getInstance() {
		return instance;
	}

	/**
	 * Creates a empty database on the system if it does not already exist. The
	 * newly created Database will have all the necessary Tables created through
	 * scripts. <br>
	 * <br>
	 * <b>Note:</b><br>
	 * This method will be called from {@link MClientApplication#onCreate()} to
	 * initialize the <code>AndroidSQLiteHelper</code>.
	 * 
	 * @throws Exception
	 */
	private void initDataBase() throws Exception {

		String path = DEFAULT_ANROID_DB_LOCATION + mContext.getPackageName()
				+ "/databases";
		// Checks if database is previously created or not.
		try {
			persistDatabase(path);
			applicationDatabase = SQLiteDatabase.openDatabase(path + "/"
					+ DATABASE_NAME, null, SQLiteDatabase.OPEN_READWRITE);
			applicationDatabase.close();
		} catch (SQLiteException e) {
			Log.e("Exception", "" + e);
		}
		applicationDatabase = getWritableDatabase();
	}

	/**
	 * @return a list of table names
	 */
	public ArrayList<String> getTableNames() {
		Cursor cursor = applicationDatabase.query("sqlite_master", null,
				"type='table'", null, null, null, null);

		ArrayList<String> tableNames = new ArrayList<String>();
		if (cursor.moveToFirst()) {
			while (!cursor.isAfterLast()) {
				tableNames.add(cursor.getString(cursor.getColumnIndex("name")));
				cursor.moveToNext();
			}
		}
		cursor.close();
		return tableNames;
	}

	/**
	 * @param path
	 * @throws Exception
	 */
	private void persistDatabase(String path) throws Exception {
		InputStream databaseFile;
		try {
			databaseFile = mContext.getAssets().open(DATABASE_NAME);

			// Open the empty db as the output stream
			File parentDir = new File(path);
			parentDir.mkdir();
			File database = new File(parentDir, DATABASE_NAME);
			if (database.exists()) {
				return;
			}
			database.createNewFile();
			OutputStream myOutput = new FileOutputStream(database);

			// transfer bytes from the inputfile to the outputfile
			byte[] buffer = new byte[1024];
			int length;
			while ((length = databaseFile.read(buffer)) > 0) {
				myOutput.write(buffer, 0, length);
			}

			// Close the streams
			myOutput.flush();
			myOutput.close();
			databaseFile.close();
		} catch (Exception e) {
			throw new Exception(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * android.database.sqlite.SQLiteOpenHelper#onCreate(android.database.sqlite
	 * .SQLiteDatabase)
	 */
	@Override
	public void onCreate(SQLiteDatabase db) {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * android.database.sqlite.SQLiteOpenHelper#onUpgrade(android.database.sqlite
	 * .SQLiteDatabase, int, int)
	 */
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

		// This assignment is done because the instance of db is different from
		// the one which is maintained (applicationDatabase). All DB operations
		// done by UpgradeManager are performed on the applicationDatabase.

		applicationDatabase = db;

		// Call onCreate() once the upgrade task is completed
		onCreate(applicationDatabase);
	}

	/**
	 * Returns table schema of table specified by <code>tableName</code> in the
	 * format of create table command.
	 * 
	 * @param tableName
	 *            name of the table whose create command is required.
	 */
	public String getTableCreateCommand(String tableName) {
		String sqlQuery = "SELECT sql FROM sqlite_master where tbl_name = "
				+ "'" + tableName + "'";
		Cursor sqlQueryCursor = applicationDatabase.rawQuery(sqlQuery, null);
		sqlQueryCursor.moveToFirst();
		String sql = sqlQueryCursor.getString(sqlQueryCursor
				.getColumnIndex("sql"));
		return sql;
	}

	/**
	 * Delete all records and insert one which is passed
	 * 
	 * @param bindObject
	 *            current record to be inserted
	 * @throws Exception
	 */
	public void deleteAllAndInsert(SOObject bindObject) throws Exception {
		applicationDatabase.delete(
				bindObject.getClass().getAnnotation(TableAnnotation.class)
						.tableName(), null, null);
		insert(bindObject);
	}

	/**
	 * Creates an insert operation on the Table(name specified through
	 * annotation on the <code>BindObject</code>).
	 * 
	 * @param bindObject
	 * @throws Exception
	 */
	public void insert(SOObject bindObject) throws Exception {

		ContentValues inputValues;
		try {
			inputValues = generateInputValues(bindObject);
			applicationDatabase.insertOrThrow(bindObject.getClass()
					.getAnnotation(TableAnnotation.class).tableName(), null,
					inputValues);
		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * Inserts a list of <code>BindObject</code>s into the Database.
	 * 
	 * @see #insert(SOObject)
	 * 
	 * @param bindObjects
	 * @throws Exception
	 */
	public void insert(List<? extends SOObject> bindObjects) throws Exception {
		for (int bindObjectIndex = 0; bindObjectIndex < bindObjects.size(); bindObjectIndex++)
			insert(bindObjects.get(bindObjectIndex));
	}

	/**
	 * Perform <tt>update</tt> operation on the Table containing the
	 * <code>bindObject</code>.
	 * 
	 * @param bindObject
	 *            updatable new bind object
	 * @param columns
	 *            column which will be referred in where clause of update
	 * @throws Exception
	 */
	public void update(SOObject bindObject, String[] columns)
			throws Exception {
		String whereClause = prepareWhereQuery(bindObject, columns);
		String[] columnValues;
		try {
			columnValues = prepareColumnValues(bindObject, columns);
			ContentValues updatableValues = generateInputValues(bindObject);

			applicationDatabase.update(
					bindObject.getClass().getAnnotation(TableAnnotation.class)
							.tableName(), updatableValues, whereClause,
					columnValues);

		} catch (Exception e) {
			throw e;
		}
	}

	/**
	 * Perform <tt>update</tt> operation on the Table containing the
	 * <code>bindObject</code> with fileds specified by <code>selection</code>
	 * to be updated.
	 * 
	 * @param bindObject
	 *            updatable new bind object
	 * @param selection
	 *            Contains name of the fields to be updated.
	 * @param columns
	 *            column which will be referred in where clause of update
	 * @throws Exception
	 */
	public void update(SOObject bindObject, String[] selection,
			String[] columns) throws Exception {
		String whereClause = prepareWhereQuery(bindObject, selection);
		String[] columnValues;
		try {
			columnValues = prepareColumnValues(bindObject, selection);
			ContentValues updatableValues = generateInputValues(bindObject,
					columns);

			applicationDatabase.update(
					bindObject.getClass().getAnnotation(TableAnnotation.class)
							.tableName(), updatableValues, whereClause,
					columnValues);

		} catch (Exception e) {
			throw (e);
		}
	}

	private ContentValues generateInputValues(SOObject bindObject,
			String[] columns) throws Exception {
		try {
			ContentValues inputValues = new ContentValues();
			Class<? extends SOObject> bindObjectClass = bindObject.getClass();
			Field[] fields = getFields(bindObjectClass);

			if (fields != null && fields.length > 0) {

				for (String column : columns) {

					Field field = bindObjectClass.getField(column);

					addFieldValueToContentValues(bindObject, inputValues, field);
				}
			}
			return inputValues;
		} catch (Exception e) {
			throw (e);
		}
	}

	private void addFieldValueToContentValues(SOObject bindObject,
			ContentValues inputValues, Field field)
			throws IllegalAccessException, IllegalArgumentException,
			IOException {

		Class<?> clazz = field.getType();
		if ((!Utility.isInaccessible(field.getModifiers()))) {
			if (clazz.equals(BigInteger.class))
				inputValues.put(field.getName(),
						((BigInteger) field.get(bindObject)).longValue());
			else if (clazz.equals(long.class) || clazz.equals(Long.class))
				inputValues.put(field.getName(), (Long) field.get(bindObject));
			// short data type support for BindObject
			else if (clazz.equals(short.class) || clazz.equals(Short.class))
				inputValues.put(field.getName(), (Short) field.get(bindObject));
			else if (clazz.equals(int.class) || clazz.equals(Integer.class))
				inputValues.put(field.getName(),
						(Integer) field.get(bindObject));
			else if (clazz.equals(byte.class) || clazz.equals(Byte.class))
				inputValues.put(field.getName(), (Byte) field.get(bindObject));
			else if (clazz.equals(boolean.class) || clazz.equals(Boolean.class))
				inputValues.put(field.getName(),
						(Boolean) field.get(bindObject));
			else if (clazz.equals(String.class))
				inputValues
						.put(field.getName(), (String) field.get(bindObject));
			else if (clazz.equals(byte[].class))
				inputValues
						.put(field.getName(), (byte[]) field.get(bindObject));
			else
				inputValues.put(field.getName(),
						convertToBytes(field.get(bindObject)));
		}
	}

	/**
	 * Perform update operation on multiple rows of a table.
	 * 
	 * @see #update(SOObject, String[])
	 * @param bindObjects
	 * @param columns
	 * @throws Exception
	 */
	public void update(List<? extends SOObject> bindObjects, String[] columns)
			throws Exception {
		for (int bindObjectIndex = 0; bindObjectIndex < bindObjects.size(); bindObjectIndex++)
			update(bindObjects.get(bindObjectIndex), columns);
	}

	/**
	 * Performs delete operation on the table(name specified through annotation
	 * on the <code>BindObject</code>) in DB which has the
	 * <code>bindObject</code>.
	 * 
	 * @param bindObject
	 *            The object with the values which should be deleted from the
	 *            DB.
	 * @param columns
	 *            the Name of the columns which are to be deleted.
	 * @throws Exception
	 */
	public void delete(SOObject bindObject, String[] columns)
			throws Exception {

		try {
			String whereClause = prepareWhereQuery(bindObject, columns);
			String[] columnValues;

			columnValues = prepareColumnValues(bindObject, columns);
			applicationDatabase.delete(
					bindObject.getClass().getAnnotation(TableAnnotation.class)
							.tableName(), whereClause, columnValues);
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * A list of <code>BindObject</code>s can be provided for deletion based on
	 * the <code>columns</code>.
	 * 
	 * @see #delete(SOObject, String[])
	 * 
	 * @param bindObjects
	 * @param columns
	 * @throws Exception
	 */
	public void delete(List<? extends SOObject> bindObjects, String[] columns)
			throws Exception {
		for (int bindObjectIndex = 0; bindObjectIndex < bindObjects.size(); bindObjectIndex++)
			delete(bindObjects.get(bindObjectIndex), columns);
	}

	/**
	 * @param tableType
	 *            table from which we need to delete all records
	 * @throws Exception
	 */
	public void deleteAll(Class<?> tableType) throws Exception {

		try {
			applicationDatabase.delete(tableType.newInstance().getClass()
					.getAnnotation(TableAnnotation.class).tableName(), null,
					null);
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * Deletes all the rows in the table.
	 * 
	 * @param tableName
	 *            the name of the table whose all rows are to be deleted.
	 * @throws Exception
	 */
	public void deleteAll(String tableName) throws Exception {
		try {
			applicationDatabase.delete(tableName, null, null);
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * Performs a <tt>select</tt> operation on the Database with the
	 * <code>bindObject</code> and <code>columns</code> specified.
	 * 
	 * @param bindObject
	 * @param columns
	 * @return Cursor pointing to the result of the <tt>select</tt> operation.
	 * @throws Exception
	 */
	public Cursor selectCursor(SOObject bindObject, String[] columns)
			throws Exception {
		try {
			String whereClause = prepareWhereQuery(bindObject, columns);
			String[] columnValues;
			columnValues = prepareColumnValues(bindObject, columns);
			Cursor cursor = applicationDatabase.query(bindObject.getClass()
					.getAnnotation(TableAnnotation.class).tableName(), null,
					whereClause, columnValues, null, null, null);
			return cursor;
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * Performs a <tt>select</tt> operation on the Database with the
	 * <code>bindObject</code> and <code>columns</code> specified.
	 * 
	 * @param bindObject
	 * @param columns
	 * @return List of <code>BindObject</code>s which is the result of the
	 *         <tt>select</tt> operation.
	 * @throws Exception
	 */
	public List<? extends SOObject> select(SOObject bindObject,
			String[] columns) throws Exception {

		try {
			Cursor cursor = selectCursor(bindObject, columns);
			return prepareBindObjectFromCursor(bindObject.getClass(), cursor);
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * @param tableType
	 *            Type of table from which we can fetch table name and find all
	 *            records from that table and returned as Bindobjects
	 * @return All rows from specific table
	 * @throws Exception
	 */
	public List<? extends SOObject> selectAll(Class<?> tableType)
			throws Exception {

		try {
			Cursor cursor = applicationDatabase.query(
					tableType.getAnnotation(TableAnnotation.class).tableName(),
					null, null, null, null, null, null);
			return prepareBindObjectFromCursor(tableType, cursor);
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * A convenience method for returning objects from a table that store only
	 * one row.
	 * 
	 * If this method is used on tables that containt multiple rows representing
	 * multiple serialized instances of the same class, then only the first one
	 * is returned.
	 * 
	 * @param clazz
	 * @return
	 * @throws Exception
	 */
	public SOObject selectSingle(Class<?> clazz) throws Exception {
		try {
			List<? extends SOObject> objectList = selectAll(clazz);
			return objectList.size() > 0 ? objectList.get(0) : null;
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * @param tableType
	 *            Type of table from which we can fetch table name and find all
	 *            records from that table
	 * @return All rows from specific table and return as cursor
	 */
	public Cursor selectAllAsCursor(Class<?> tableType)
			throws IllegalArgumentException, IllegalAccessException,
			InstantiationException {
		Cursor cursor = applicationDatabase.query(tableType.newInstance()
				.getClass().getAnnotation(TableAnnotation.class).tableName(),
				null, null, null, null, null, null);
		return cursor;
	}

	/**
	 * @param tableType
	 *            the table from which we need to find total records
	 * @return total number of records in the table
	 */
	public int getRecordCount(Class<?> tableType) {
		Cursor cursor = applicationDatabase.query(((TableAnnotation) tableType
				.getAnnotation(TableAnnotation.class)).tableName(), null, null,
				null, null, null, null);
		int count = cursor.getCount();
		return count;
	}

	/**
	 * This method is used to perform conditional count operation on a Table.
	 * 
	 * @param object
	 * @param columns
	 *            the columns on which condition for slection is to be applied
	 * @return the number of records in the specified <code>columns</code> with
	 *         the specified values in <code>object</code>.
	 * @throws Exception
	 */
	public int getRecordCount(SOObject object, String[] columns)
			throws Exception {
		return (select(object, columns)).size();
	}

	/**
	 * Prepares BindObject from the <code>Cursor</code>. The BindObject type to
	 * be prepared is obtained from the specified <code>bindObject</code>
	 * 
	 * @param bindObjectClass
	 * @param cursor
	 * @return
	 * @throws Exception
	 */
	private List<? extends SOObject> prepareBindObjectFromCursor(
			Class<?> bindObjectClass, Cursor cursor) throws Exception {

		try {

			List<SOObject> bindObjects = new ArrayList<SOObject>();

			while (cursor.moveToNext()) {
				SOObject convertedObject = (SOObject) bindObjectClass
						.newInstance();
				Field[] fields = getFields(bindObjectClass);
				// NOTE: The column index is obtained here by the columnName to
				// match proper index to obtain data from the cursor.
				if (fields != null && fields.length > 0) {
					for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
						Class<?> clazz = fields[fieldIndex].getType();
						if (!Utility.isInaccessible(fields[fieldIndex]
								.getModifiers())) {

							if (clazz.equals(BigInteger.class))
								fields[fieldIndex]
										.set(convertedObject,
												cursor.getLong(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							else if (clazz.equals(long.class)
									|| clazz.equals(Long.class))
								fields[fieldIndex]
										.setLong(
												convertedObject,
												cursor.getLong(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							// short data type support for BindObject
							else if (clazz.equals(short.class)
									|| clazz.equals(Short.class))
								fields[fieldIndex]
										.setShort(
												convertedObject,
												cursor.getShort(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							else if (clazz.equals(int.class)
									|| clazz.equals(Integer.class))
								fields[fieldIndex]
										.setInt(convertedObject,
												cursor.getInt(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							else if (clazz.equals(byte.class)
									|| clazz.equals(Byte.class))
								fields[fieldIndex]
										.set(convertedObject,
												cursor.getShort(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							else if (clazz.equals(boolean.class)
									|| clazz.equals(Boolean.class)) {

								int fieldValue = cursor.getInt(cursor
										.getColumnIndex(fields[fieldIndex]
												.getName()));
								if (fieldValue == 1)
									fields[fieldIndex].set(convertedObject,
											Boolean.TRUE);
								else
									fields[fieldIndex].set(convertedObject,
											Boolean.FALSE);

							} else if (clazz.equals(String.class))
								fields[fieldIndex]
										.set(convertedObject,
												cursor.getString(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							else if (clazz.equals(byte[].class))
								fields[fieldIndex]
										.set(convertedObject,
												cursor.getBlob(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName())));
							else
								fields[fieldIndex]
										.set(convertedObject,
												convertToObject(cursor.getBlob(cursor
														.getColumnIndex(fields[fieldIndex]
																.getName()))));
						}
					}
				}
				bindObjects.add(convertedObject);
			}
			cursor.close();
			return bindObjects;
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * Returns field array of <code>bindObjectClass</code> based on
	 * {@link TableAnnotation#isInheritanceSupported()} annotation of
	 * <code>bindObjectClass</code>
	 * 
	 * @param bindObjectClass
	 *            class which extends {@link SOObject}.
	 */
	private Field[] getFields(Class<?> bindObjectClass) {
		Field[] fields = null;
		if (bindObjectClass.getAnnotation(TableAnnotation.class)
				.isInheritanceSupported()) {
			fields = bindObjectClass.getFields();
		} else {
			fields = bindObjectClass.getDeclaredFields();
		}
		return fields;
	}

	/**
	 * Prepares a WHERE clause based on the columns provided.<br>
	 * i.e. the columns will be searched for in the bind object specified and an
	 * SQL WHERE query is generated.<br>
	 * <br>
	 * <b>Example:</b><br>
	 * If the Bind object provided is:<br>
	 * <code>String fname;<br> int id;<br> String lname;<br><br></code> as its
	 * attributes and <code>columns</code> has the <code>id</code>, the WHERE
	 * query will be generated as follows: <code>WHERE id=?</code>
	 * 
	 * @param bindObject
	 * @param columns
	 * @return the WHERE clause for execution of SQLite Query
	 */
	private String prepareWhereQuery(SOObject bindObject, String[] columns) {
		String whereQuery = "";
		if (columns != null) {
			for (int columnIndex = 0; columnIndex < columns.length; columnIndex++) {
				if (whereQuery.length() == 0)
					whereQuery = whereQuery.concat(columns[columnIndex] + "=?");
				else
					whereQuery = whereQuery.concat(" AND "
							+ columns[columnIndex] + "=?");
			}
		}
		// null should be return to fix it in condition for
		// android sqlite database function
		if (whereQuery.trim().length() == 0)
			return null;
		return whereQuery;
	}

	/**
	 * Prepares column values(WHERE arguments) based on the <code>columns</code>
	 * provided on the <code>bindObject</code>. These column values will be
	 * inserted in the specified <code>columns</code>. <br>
	 * <br>
	 * <b>Example:</b> <br>
	 * <br>
	 * If the <code>BindObject</code> provided is an <code>Object</code> with
	 * the fields int id and String name and the row with id=4 needs to be
	 * deleted.<br>
	 * Now, the columns[] will have "id" as its string content.<br>
	 * The columnValues[] will have the following content :<tt>"4"</tt> <br>
	 * 
	 * @param bindObject
	 * @param columns
	 * @return
	 * @throws Exception
	 */
	private String[] prepareColumnValues(SOObject bindObject, String[] columns)
			throws Exception {
		try {
			String[] columnValues = null;
			if (columns != null) {
				columnValues = new String[columns.length];
				Class<? extends SOObject> objectClass = bindObject.getClass();
				boolean inheritanceSupported = objectClass.getAnnotation(
						TableAnnotation.class).isInheritanceSupported();
				for (int columnIndex = 0; columnIndex < columns.length; columnIndex++) {
					Field field;
					if (inheritanceSupported)
						field = objectClass.getField(columns[columnIndex]);
					else
						field = objectClass
								.getDeclaredField(columns[columnIndex]);
					Object columnValue = field.get(bindObject);
					columnValues[columnIndex] = columnValue == null ? ""
							: columnValue.toString();
				}
			}
			return columnValues;
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * Generates input values for the <code>COntentValues</code> to be inserted
	 * in the database. This method converts the input values to appropriate
	 * content value types.
	 * 
	 * @param bindObject
	 * @return
	 * @throws Exception
	 */
	private ContentValues generateInputValues(SOObject bindObject)
			throws Exception {

		try {
			ContentValues inputValues = new ContentValues();
			Class<? extends SOObject> bindObjectClass = bindObject.getClass();
			Field[] fields = getFields(bindObjectClass);

			if (fields != null && fields.length > 0) {

				for (int fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {

					Field field = fields[fieldIndex];
					addFieldValueToContentValues(bindObject, inputValues, field);
				}
			}
			return inputValues;
		} catch (Exception e) {
			throw (e);
		}
	}

	/**
	 * De-serializes a serialized byte array to the original object.
	 * 
	 * @param bytes
	 *            a serialized byte array
	 * @return the de-serialized object
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	private Object convertToObject(byte[] bytes) throws ClassNotFoundException,
			IOException {
		ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
		ObjectInputStream objectStream;
		try {
			objectStream = new ObjectInputStream(byteStream);
			return objectStream.readObject();
		} catch (IOException e) {
			throw e;
		} catch (ClassNotFoundException e) {
			throw (e);
		}

	}

	/**
	 * Converts an object into a serialized byte array.
	 * 
	 * @param object
	 *            Object to be converted
	 * @return a serialized byte array
	 * @throws IOException
	 */
	private byte[] convertToBytes(Object object) throws IOException {
		ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
		ObjectOutputStream objectStream;
		try {
			objectStream = new ObjectOutputStream(byteStream);
			objectStream.writeObject(object);
			objectStream.flush();
			objectStream.close();
			return byteStream.toByteArray();
		} catch (IOException e) {
			throw (e);
		}

	}

	/**
	 * Creates an SQL statement into a reusable pre-compiled statement object.
	 * You may put ?s in the statement and fill in those values with
	 * <code>SQLiteProgram.bindLong()</code> and
	 * <code>SQLiteProgram.bindLong()</code> each time you want to run the
	 * statement. Statements may not return result sets larger than 1x1.
	 * 
	 * @param sql
	 *            The raw SQL statement, may contain ? for unknown values to be
	 *            bound later.
	 * @return a pre-compiled statement object.
	 */
	public SQLiteStatement createStatementRaw(String sql)
			throws SQLiteException {
		SQLiteStatement sqlStatement = null;
		sqlStatement = applicationDatabase.compileStatement(sql);
		return sqlStatement;
	}

	/**
	 * Execute this SQL statement, if it is not a query. For example, CREATE
	 * TABLE, DELTE, INSERT, etc.
	 * 
	 * @throws android.database.SQLException
	 *             If the SQL string is invalid for some reason
	 */
	public void executeRaw(SQLiteStatement sqlStatement) {
		sqlStatement.execute();
	}

	public void execSQL(String sql) {
		applicationDatabase.execSQL(sql);
	}

	/**
	 * Runs the provided SQL and returns a <code>Cursor</code> over the result
	 * set.
	 * 
	 * @param statement
	 *            the SQL query. The SQL string must not be ; terminated
	 * @param selectionArgs
	 *            You may include ?s in where clause in the query, which will be
	 *            replaced by the values from selectionArgs. The values will be
	 *            bound as Strings.
	 * @return A <code>Cursor</code> object, which is positioned before the
	 *         first entry
	 */
	public Cursor getCursorRaw(String statement, String[] selectionArgs) {
		Cursor cursor = applicationDatabase.rawQuery(statement, selectionArgs);
		return cursor;
	}

	public void beginTransaction() {
		applicationDatabase.beginTransaction();
	}

	public void endTransaction() {
		applicationDatabase.setTransactionSuccessful();
		applicationDatabase.endTransaction();
	}

	/**
	 * @param tableType
	 *            Type of table from which we can fetch table name and find all
	 *            records from that table and returned as Bindobjects
	 * @return All rows from specific table
	 * @throws Exception
	 */
	public List<? extends SOObject> selectAllUnique(Class<?> tableType,
			String groupBy) throws Exception {

		try {
			Cursor cursor = applicationDatabase.query(true, tableType
					.getAnnotation(TableAnnotation.class).tableName(), null,
					null, null, groupBy, null, null, null);
			return prepareBindObjectFromCursor(tableType, cursor);
		} catch (Exception e) {
			throw (e);
		}
	}
}
